﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using CxExtension.Hashset;
using JetBrains.Annotations;
using UnityEngine;

namespace CxExtension
{
	public class CompareResult<T>
	{
		public HashSet<T> leftOnly = new HashSet<T>();
		public HashSet<T> bothHave = new HashSet<T>();
		public HashSet<T> rightOnly = new HashSet<T>();
		
	}
	public class DirectoryUtil
	{
		private static readonly Type type = typeof(Directory);
		private static readonly string searchPattenAll = "*";

		public static CompareResult<string> CompareDirectory(string src, string dest,Func<string,string,bool> filter)
		{
			var srcList = Directory.GetFiles(src,"*",SearchOption.AllDirectories);
			var destList = Directory.GetFiles(dest,"*",SearchOption.AllDirectories);
			var srcHash = srcList.ToHashSet((it)=> PathUtil.RelativePath(it,src));
			var destHash = destList.ToHashSet((it)=> PathUtil.RelativePath(it,dest));
			var result = new CompareResult<string>();
			var leftOnly = result.leftOnly;
			var bothHave = result.bothHave;
			var rightOnly = result.rightOnly;
			srcHash.CompareDiff(destHash,leftOnly,bothHave,rightOnly);
			return result;
		}

		public static void DeleteContent(string dir)
		{
			DirectoryUtil.EnsureNew(dir);
		}
		public static bool Copy(string src, string dest)
		{
			return Copy(src, dest, searchPattenAll, SearchOption.AllDirectories);
		}

		public static bool Copy([NotNull] string src, [NotNull] string dest, [NotNull] string searchPatten,
			SearchOption searchOption)
		{
			return Copy(src, dest, searchPatten, searchOption, null);
		}

		public static bool Copy([NotNull] string src, [NotNull] string dest, string searchPatten, 
			Func<string, bool> fileSelection = null,
			Func<string, string> pathFunc =null, SearchOption searchOption = SearchOption.AllDirectories, bool forceOverCopy = false, bool removeSrcUnExist = false,
			Func<string, bool> deleteSelection = null)
		{
			return Copy(src, dest, searchPatten, searchOption, pathFunc, forceOverCopy, removeSrcUnExist,
				deleteSelection, fileSelection);
		}
		/// <summary>
		/// copy directory
		/// </summary>
		/// <param name="src"></param>
		/// <param name="dest"></param>
		/// <param name="searchPatten"></param>
		/// <param name="searchOption"></param>
		/// <param name="pathFunc"></param>
		/// <param name="forceOverCopy"></param>
		/// <param name="removeSrcUnExist"></param>
		/// <param name="deleteSelection"></param>
		/// <param name="fileSelection">file filter if return true then ignore file</param>
		/// <returns></returns>
		public static bool Copy([NotNull] string src, [NotNull] string dest, [NotNull] string searchPatten, SearchOption searchOption,
			Func<string, string> pathFunc,bool forceOverCopy = false,bool removeSrcUnExist = false,
			Func<string,bool> deleteSelection = null, Func<string, bool> fileSelection = null)
		{
			if (src == null) throw new ArgumentNullException("src");
			if (dest == null) throw new ArgumentNullException("dest");
			if (searchPatten == null) throw new ArgumentNullException("searchPatten");
			if (!Enum.IsDefined(typeof(SearchOption), searchOption))
				throw new InvalidEnumArgumentException("searchOption", (int)searchOption, typeof(SearchOption));

			var dirList = Directory.GetDirectories(src, searchPattenAll, searchOption);
			foreach (var it in dirList)
			{
				if (fileSelection != null && fileSelection(it) == false)
				{
					continue;
				}
				var newDir = PathUtil.ChangeRootPath(it, src, dest);
				FileUtil.DeleteSafe(newDir);
				EnsureExits(newDir);
			}
			var fileIt = Directory.GetFiles(src, searchPatten, searchOption);
			var copyCount = 0;

			Debug.LogFormat("copy start,check:{0}", fileIt.Length);
			HashSet<string> destHash = new HashSet<string>();
			if (removeSrcUnExist && Directory.Exists(dest))
			{
				var destFileList = Directory.GetFiles(dest, searchPatten, searchOption);
				destFileList.ToHashSet((PathUtil.ToUnixPath),destHash);

			}
			else
			{
				var destFullPath = Path.GetFullPath(dest);
				Debug.LogFormat("Path:{0} \nFull :{1}", dest,destFullPath);
				
				Directory.CreateDirectory(destFullPath);
			}
			foreach (var it in fileIt)
			{
				if (fileSelection != null && fileSelection(it) == false)
				{
					continue;
				}
				var newDest = PathUtil.ChangeRootPath(it, src, dest);
				if (pathFunc != null)
				{
					newDest = pathFunc(newDest);
				}

				if (removeSrcUnExist)
				{
					destHash.Remove(newDest);
				}
				//continue;
				var copy = FileUtil.Copy(it, newDest, forceOverCopy);
				if (copy)
				{
					copyCount++;
					//Debug.LogFormat("copy file {2} from:{0}\nto:{1}", it,newpath,copyCount);
				}
			}

			int removeCount = 0;
			if (removeSrcUnExist && destHash.Count > 0)
			{
				foreach (var it in destHash)
				{
					bool delete = true;
					if (deleteSelection != null)
					{
						delete = deleteSelection(it);
					}
					if (delete)
					{
						File.Delete(it);
						removeCount++;
					}
				}

				DirectoryUtil.DeleteEmptyDir(dest);
			}

			Debug.LogFormat("copy complete,copy:{0}; remove:{1}", copyCount, removeCount);

			return true;
		}
		/// <summary>
		/// if path not exits then create it
		/// </summary>
		/// <param name="path"></param>
		public static void EnsureExits(string path)
		{
			if (Directory.Exists(path)) return;
			Directory.CreateDirectory(path);

		}
		/// <summary>
		/// 删除空文件
		/// </summary>
		/// <param name="path"></param>
		/// <returns></returns>
		public static int DeleteEmptyDir(string path,Action<string> deleteAction = null)
		{
			int itemNum = 0;

			var files = Directory.GetFiles(path);
			itemNum += files.Length;

			var dirs = Directory.GetDirectories(path);
			if (dirs.Length > 0)
			{
				foreach (var dir in dirs)
				{
					itemNum += DeleteEmptyDir(dir,deleteAction);
				}
			}
			if (itemNum == 0)
			{
				Directory.Delete(path);
				if (deleteAction != null) deleteAction(path);
			}
			return itemNum;
		}
		public static void EnsureNew(string path)
		{
			if (Directory.Exists(path))
			{
				Directory.Delete(path, true);
			}

			Directory.CreateDirectory(path);
		}
	}
}